package org.ragdollcat.first.manager.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.ragdollcat.first.exception.ErrorCode;
import org.ragdollcat.first.exception.ThrowUtils;
import org.ragdollcat.first.manager.disruptor.EditPictureDisruptorEvent;
import org.ragdollcat.first.manager.disruptor.EditPictureEventProducer;
import org.ragdollcat.first.model.entity.User;
import org.ragdollcat.first.model.enums.PictureEditActionEnum;
import org.ragdollcat.first.model.enums.PictureEditMessageTypeEnum;
import org.ragdollcat.first.service.UserService;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


@Component
public class PictureEditHandler extends TextWebSocketHandler {

    /**
     * 当前正在编辑某个图片的用户的集合
     * 用于控制同一时刻只能有一个用户操作图片
     */
    private final Map<Long, Long> pictureEditUsers = new ConcurrentHashMap<>(1);

    /**
     * 当前某个图片所有参与编辑的session的集合
     */
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>(16);

    /**
     * 扩展点保存某个用户的操作
     * key : 用户ID value：执行的操作
     */
    private final Map<Long, LinkedList<String>> userActions = new ConcurrentHashMap<>(16);

    @Resource
    private UserService userService;

    @Resource
    private EditPictureEventProducer editPictureEventProducer;

    /**
     * 连接建立
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        Map<String, Object> attributes = session.getAttributes();

        User user = (User) attributes.get("user");
        Long picturedId = (Long) attributes.get("pictureId");

        //初始化pictureSessions
        pictureSessions.putIfAbsent(picturedId, ConcurrentHashMap.newKeySet());
        //将当前session存入pictureSessions的picture对应的set中
        pictureSessions.get(picturedId).add(session);
        //todo 应对第一个用户点击图片编辑并且进入了编辑 第二个用户点击图片编辑，不显示当前正在编辑用户的境况
        //todo 这里取正在编辑图片的用户的信息，给前端展示
        User currentEditUser = null;
        Long userId = null;
        if (CollUtil.isNotEmpty(pictureEditUsers)) {
            userId = pictureEditUsers.get(picturedId);
            currentEditUser = userService.getById(userId);
        }

        //todo 应对第一个用户点击图片编辑并且进入了编辑 第二个用户点击图片编辑，不显示当前正在编辑的操作的境况
        //构造响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(
                PictureEditMessageTypeEnum.INFO.getValue(),
                String.format("用户 %s 进入编辑", user.getUserName()),
                null,
                userService.castUserToUserVo(user),
                ObjUtil.isNotEmpty(currentEditUser) ? userService.castUserToUserVo(currentEditUser) : null,
                ObjUtil.isNotEmpty(userId) ? userActions.get(userId) : null);
        //广播消息,给同一张图片的用户
        broadcastResponseMessage(picturedId, pictureEditResponseMessage);
    }


    /**
     * 处理前端传递的信息
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        super.handleMessage(session, message);
        //转换请求为对象
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload().toString(), PictureEditRequestMessage.class);
        Map<String, Object> attributes = session.getAttributes();
        User user = (User) attributes.get("user");
        Long pictureId = (Long) attributes.get("pictureId");

        EditPictureDisruptorEvent editPictureDisruptorEvent = new EditPictureDisruptorEvent(session, pictureId, user, pictureEditRequestMessage);

        //发送事件
        editPictureEventProducer.publishEvent(editPictureDisruptorEvent);

    }

    /**
     * 处理退出编辑
     *
     * @param user
     * @param picturedId
     */
    public void handlerExit(User user, Long picturedId) throws IOException {
        Long userId = pictureEditUsers.get(picturedId);
        //控制同一 时刻只能有一个用户在编辑
        if (userId != null && !user.getId().equals(userId)) {
            return;
        }
        pictureEditUsers.remove(picturedId);
        //构造响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(
                PictureEditMessageTypeEnum.EXIT_EDIT.getValue(),
                String.format("用户 %s 退出编辑图片", user.getUserName()),
                null,
                userService.castUserToUserVo(user), null, null);
        //广播
        broadcastResponseMessage(picturedId, pictureEditResponseMessage);
    }

    /**
     * 处理编辑
     */
    public void handlerEdit(User user, Long picturedId, WebSocketSession session, String editAction) throws IOException {
        Long userId = pictureEditUsers.get(picturedId);
        //控制同一时刻只能有一个用户在编辑
        if (!user.getId().equals(userId)) {
            return;
        }
        PictureEditActionEnum enumByValue = PictureEditActionEnum.getEnumByValue(editAction);
        ThrowUtils.throwIf(ObjUtil.isEmpty(enumByValue), ErrorCode.PARAMS_ERROR, "编辑操作类型不合理！");

        //这里需要保存用户的操作
        userActions.putIfAbsent(userId, new LinkedList<>());
        userActions.get(userId).add(enumByValue.getValue());


        //构造响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(
                PictureEditMessageTypeEnum.EDIT_ACTION.getValue(),
                String.format("用户 %s 编辑图片，执行 %s ", user.getUserName(), enumByValue.getText()),
                editAction,
                userService.castUserToUserVo(user),
                null,
                null);
        //广播给除了自己的其他用户,防止自身重复执行编辑操作
        broadcastResponseMessage(picturedId, pictureEditResponseMessage, session);


    }

    /**
     * 处理进入编辑状态
     */
    public void handlerEnter(User user, Long picturedId) throws IOException {
        //首先要将自身加入pictureEditUsers
        if (!pictureEditUsers.containsKey(picturedId)) {
            pictureEditUsers.put(picturedId, user.getId());
            //构造响应
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(
                    PictureEditMessageTypeEnum.ENTER_EDIT.getValue(),
                    String.format("用户 %s 开始编辑图片", user.getUserName()),
                    null,
                    userService.castUserToUserVo(user), null, null);
            //广播
            broadcastResponseMessage(picturedId, pictureEditResponseMessage);
        }
    }

    /**
     * 连接关闭 点确认或者关闭弹窗时触发
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        Map<String, Object> attributes = session.getAttributes();
        User user = (User) attributes.get("user");
        Long picturedId = (Long) attributes.get("pictureId");

        //这里不仅仅要把后端map中的值清空，也需要发送通知给前端把当前编辑人置空
        handlerExit(user, picturedId);


        Set<WebSocketSession> webSocketSessions = pictureSessions.get(picturedId);
        if (webSocketSessions != null) {
            webSocketSessions.remove(session);
            //当webSocketSessions集合为空时，将其从map中移除
            if (CollUtil.isEmpty(webSocketSessions)) {
                pictureSessions.remove(picturedId);
            }
        }

        //清理掉用户操作
        userActions.clear();

        //构造响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage(
                PictureEditMessageTypeEnum.INFO.getValue(),
                String.format("用户 %s 退出编辑", user.getUserName()),
                null,
                userService.castUserToUserVo(user), null, null);

        //广播消息,给同一张图片的用户
        broadcastResponseMessage(picturedId, pictureEditResponseMessage);
    }

    /**
     * 广播消息给同一张图片的用户,排除自身
     *
     * @param picturedId
     * @param pictureEditResponseMessage
     * @param excludeSession
     */
    public void broadcastResponseMessage(Long picturedId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws IOException {
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(picturedId);
        if (CollUtil.isNotEmpty(webSocketSessions)) {
            for (WebSocketSession webSocketSession : webSocketSessions) {
                //排除自身
                if (ObjUtil.isNotEmpty(excludeSession) && excludeSession.equals(webSocketSession)) {
                    continue;
                }
                if (webSocketSession.isOpen()) {
                    //发送消息
                    webSocketSession.sendMessage(castRespToJson(pictureEditResponseMessage));
                }
            }
        }
    }

    /**
     * 将响应消息序列化为json
     *
     * @param pictureEditResponseMessage
     * @return
     */
    private WebSocketMessage<?> castRespToJson(PictureEditResponseMessage pictureEditResponseMessage) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(module);
        String json = objectMapper.writeValueAsString(pictureEditResponseMessage);
        return new TextMessage(json);
    }


    /**
     * 广播消息给同一张图片的用户,不排除自身
     *
     * @param picturedId
     * @param pictureEditResponseMessage
     */
    public void broadcastResponseMessage(Long picturedId, PictureEditResponseMessage pictureEditResponseMessage) throws IOException {
        broadcastResponseMessage(picturedId, pictureEditResponseMessage, null);
    }

}
